#pragma once 

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <string.h>
#include <iostream>
#include <string>
#include <unordered_map>

#include "ConnectInfo.hpp"

/* 保存多个用户的信息 
 * key-value 结构体用来保存用户的信息
 *    key：用户id
 *    value：用来代表单个用户的信息
 *      eg：1:{nickname, school, passwd}
 *          2:{nickname, school, passwd}
 * 
 * 不好的地方：
 *          每次重启服务进程之后，之前注册的所有用户都没有了(之前注册的用户没有持久化)
 * 解决方案：
 *          将之前注册的数据放到数据库当中保存
 *          启动服务的时候，需要去数据库当中获取之前注册用户的信息，加载到进程内存当中，在判断登录的时候，不需要查询数据库，用户访问就比较快捷
 *          注册完毕之后，还需要将数据及时写到数据库当中(持久化)
 */

class UserInfo
{
    public:
        UserInfo(const std::string& nick_name, const std::string& school, const std::string& passwd, uint32_t user_id)
        {
            //对成员变量进行填充
            nick_name_ = nick_name;
            school_ = school;
            passwd_ = passwd;
            user_id_ = user_id;
            user_status_ = REGISTER_FAILED; //开始给一个注册失败的值

            //新增udp地址信息的初始化
            memset((void*)&addr_, '\0', sizeof(struct sockaddr_in));
            addr_len_ = 0;
        }

        ~UserInfo()
        {}

        std::string& GetPasswd()
        {
            return passwd_;
        }

        void SetUserStatus(int status)
        {
            user_status_ = status;
        }

        int GetUserStatus()
        {
            return user_status_;
        }

        void SetaddrInfo(struct sockaddr_in addr)
        {
            memcpy(&addr_, &addr, sizeof(addr));
        }

        void SetaddrLenInfo(socklen_t addr_len)
        {
            addr_len_ = addr_len;
        }

        struct sockaddr_in& GetAddrInfo()
        {
            return addr_;
        }

        socklen_t GetAddrLen()
        {
            return addr_len_;
        }
    private: 
        std::string nick_name_;
        std::string school_;
        std::string passwd_;

        //用户id
        uint32_t user_id_;

        //用户状态
        int user_status_;

        //新增udp地址信息
        struct sockaddr_in addr_;
        socklen_t addr_len_;
};


//保存用户信息
class UserManager
{
    public:
        UserManager()
        {
            user_map_.clear(); //清空
            //STL都不是线程安全的，需要加锁
            pthread_mutex_init(&map_lock_, NULL); //对锁进行初始化
            prepare_id_ = 0;

            Online_user_.clear(); //清空
        }

        ~UserManager()
        {
            pthread_mutex_destroy(&map_lock_); //销毁互斥锁
        }

        //处理注册的请求
        //user_id是出参，返回给调用者
        int DealRegister(const std::string& nick_name, const std::string& school, const std::string& passwd, uint32_t* user_id)  //将char字符数组直接传入到string里面
        {
            //1.判断昵称、学校、密码字段是否为空 
            if(nick_name.size() == 0 || school.size() == 0 || passwd.size() == 0)
            {
                return -1;
            }

            //2.创建单个用户UserInfo这个类的对象
            //  创建用户，分配id时，这个id也是临界资源，如果多个线程同时访问预分配的id时，可能拿到的是同一个id，出现线程不安全的现象，需要加锁，保证是一个确定的独有的id
            pthread_mutex_lock(&map_lock_); //加锁

            //3.分配用户id
            UserInfo ui(nick_name, school, passwd, prepare_id_); 
            *user_id = prepare_id_;//在更改prepare_id_之前赋值给user_id

            //4.更改当前用户的状态
            ui.SetUserStatus(REGISTER_SUCCESS);  //注册成功
            
            //5.将用户的数据插入到map当中
            user_map_.insert(std::make_pair(prepare_id_, ui)); //用户信息保存到map当中
            
            //6.更新预分配的用户id
            prepare_id_++;
            pthread_mutex_unlock(&map_lock_); //解锁
            return 0;
        }

        //处理登录的请求
        int DealLogin(uint32_t id, const std::string& passwd)
        {
            //1.判断passwd是否为空
            //2.使用id，在unordered_map当中查找是否有该ID对应的值
            //  2.1没找到该ID对应的值，返回登陆失败
            //  2.2找到了ID对应的值
            //      对比保存的密码的值和上次提交注册的密码的值是否一致
            //        a.如果不一致，则登陆失败
            //        b.如果一致，则登陆成功
            
            if(passwd.size() == 0)
            {
                return -1;
            }

            std::unordered_map<uint32_t, UserInfo>::iterator iter; //迭代器变量iter

            pthread_mutex_lock(&map_lock_);

            iter = user_map_.find(id);
            if(iter == user_map_.end())
            {
                //没查找到
                pthread_mutex_unlock(&map_lock_);
                return -2;
            }

            //查找到了，比较密码
            std::string reg_passwd = iter->second.GetPasswd(); 
            if(reg_passwd != passwd)
            {
                iter->second.SetUserStatus(LOGIN_FAILED); //登陆失败
                pthread_mutex_unlock(&map_lock_); //释放锁
                return -3;
            }
            
            //对比密码成功了
            iter->second.SetUserStatus(LOGIN_SUCCESS); //登陆成功
            pthread_mutex_unlock(&map_lock_);
            return 0;
        }


        //user_id：用户id
        //addr：udp客户端的地址信息，是为了后面推送消息所保存的
        //addrlen：udp客户端的地址信息长度
        int IsLogin(uint32_t user_id, struct sockaddr_in addr, socklen_t addr_len)
        {
            //1.使用user_id去unordered_map当中查询，是否存在该用户
            //  如果存在，则获取用户信息，判断用户状态
            //  如果不存在，直接返回，刚刚接收到的udp数据直接丢掉
            std::unordered_map<uint32_t, UserInfo>::iterator iter;
            pthread_mutex_lock(&map_lock_);
            iter = user_map_.find(user_id);
            if(iter == user_map_.end())
            {
                pthread_mutex_unlock(&map_lock_);
                return -1;
            }
            UserInfo ui = iter->second;
            //2.判断用户状态
            //  第一次发送，则我们保存该用户的地址信息
            //  如果是第n次发送，则不用添加地址信息
            if(ui.GetUserStatus() <= LOGIN_FAILED) 
            {
                //排除其他状态的情况
                pthread_mutex_unlock(&map_lock_); 
                return -1;
            }
            else if(ui.GetUserStatus() == LOGIN_SUCCESS)
            {
                //第一次发送udp消息(刚刚登录完成)
               ui.SetUserStatus(ONLINE); //将状态改为在线状态
               ui.SetaddrInfo(addr); //保存地址信息，后面推送消息需要用到
               ui.SetaddrLenInfo(addr_len);
               //将该用户的信息增加到在线用户列表当中
               //本质上是为推送消息到udp客户端做铺垫
               Online_user_.push_back(ui);
            }

            pthread_mutex_unlock(&map_lock_);
            return 0;
        }

        void GetOnlineUser(std::vector<UserInfo>* vec)
        {
            *vec = Online_user_;
        }

    private:
        //uint32_t  -->  id 
        //UserInfo  -->   保存具体的用户信息
        std::unordered_map<uint32_t, UserInfo> user_map_; //key就是id,value就是定义的UserInfo

        pthread_mutex_t map_lock_; //互斥锁用来保护这个user_map_的

        //预分配的用户id，当用户管理模块接收到注册请求之后，将prepare_id分配给注册用户，分配完毕之后，需要对prepare_id进行更新
        uint32_t prepare_id_;

        //保存的都是在线用户
        std::vector<UserInfo> Online_user_;
};
